{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### 在API测试中使用YAML管理测试用例\n",
    "在接口测试中，管理大量的接口信息和测试用例是一项复杂的任务。为了提高可读性和可维护性，使用YAML文件来管理这些信息是一个不错的选择。\n",
    "\n",
    "#### 忽略HTTPS证书验证\n",
    "使用requests模块发送HTTPS请求时，如果目标网站的SSL证书不被信任，可能会导致请求失败。为了解决这个问题，可以在请求中添加verify = False 参数，忽略SSL证书验证。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "d:\\miniconda\\envs\\deeppacket\\lib\\site-packages\\urllib3\\connectionpool.py:1071: InsecureRequestWarning: Unverified HTTPS request is being made to host 'www.bing.com'. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/1.26.x/advanced-usage.html#ssl-warnings\n",
      "  InsecureRequestWarning,\n",
      "d:\\miniconda\\envs\\deeppacket\\lib\\site-packages\\urllib3\\connectionpool.py:1071: InsecureRequestWarning: Unverified HTTPS request is being made to host 'cn.bing.com'. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/1.26.x/advanced-usage.html#ssl-warnings\n",
      "  InsecureRequestWarning,\n"
     ]
    }
   ],
   "source": [
    "import requests\n",
    "\n",
    "response = requests.get('https://www.bing.com', verify = False)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "注意：在生产环境中，忽略SSL证书可能会带来安全风险，建议仅在测试环境中使用。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### 了解YAML文件\n",
    "##### 生么是YAML？\n",
    "YAML(YAML Ain't Markup Language)是一种专门用来写配置文件的语言，具有简洁、易读的特点。相比JSON，YAML更加人性化，适合用来编写配置文件和数据交换。\n",
    "##### YAML的基本语法\n",
    "- 大小写敏感\n",
    "- 使用缩进表示层级关系\n",
    "- 不允许使用制表符进行缩进，只能使用空格\n",
    "##### 基本结构\n",
    "1) 键值对 \n",
    "```yaml\n",
    "    key: value\n",
    "```\n",
    "2) 嵌套结构\n",
    "```yaml\n",
    "    parent\n",
    "      child1: value1\n",
    "      child2: value2\n",
    "```\n",
    "2) 列表\n",
    "```yaml\n",
    "    items:\n",
    "      - item1\n",
    "      - item2\n",
    "      - item3\n",
    "```\n",
    "注意：冒号后面必须有一个空格，否则会导致语法错误\n",
    "4) 注释\n",
    "使用#进行单行注释\n",
    "```yaml\n",
    "    # 这是一个注释\n",
    "    key: value # 行尾注释\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### 编写接口测试用例的YAML文件\n",
    "以用户登录接口为例，演示如何将接口信息和测试用例写入YAML文件。\n",
    "##### 定义基本结构\n",
    "创建一个名为login.yaml的文件，内容如下\n",
    "```yaml\n",
    "# 接口基本信息\n",
    "base_info:\n",
    "  api_name: 用户登录\n",
    "  url: /login\n",
    "  method: post\n",
    "  headers:\n",
    "    Content-Type: application/x-www-form-urlencoded\n",
    "```\n",
    "##### 编写测试用例\n",
    "```yaml\n",
    "# 测试用例\n",
    "test_cases:\n",
    "  - case_name: 正确的用户名和密码\n",
    "    data:\n",
    "      username: test_user\n",
    "      password: test_pass\n",
    "    validations:\n",
    "      - contains: 登录成功\n",
    "    extract:\n",
    "      - token\n",
    "\n",
    "  - case_name: 错误的用户名\n",
    "    data:\n",
    "      username: wrong_user\n",
    "      password: test_pass\n",
    "    validations:\n",
    "      - contains: 用户名或密码错误\n",
    "```\n",
    "##### 完整的YAML文件\n",
    "```yaml\n",
    "# login.yaml\n",
    "base_info:\n",
    "  api_name: 用户登录\n",
    "  url: /login\n",
    "  method: post\n",
    "  headers:\n",
    "    Content-Type: application/x-www-form-urlencoded\n",
    "\n",
    "test_cases:\n",
    "  - case_name: 正确的用户名和密码\n",
    "    data:\n",
    "      username: test_user\n",
    "      password: test_pass\n",
    "    validations:\n",
    "      - contains: 登录成功\n",
    "    extract:\n",
    "      - token\n",
    "\n",
    "  - case_name: 错误的用户名\n",
    "    data:\n",
    "      username: wrong_user\n",
    "      password: test_pass\n",
    "    validations:\n",
    "      - contains: 用户名或密码错误\n",
    "\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### 在python中读取YAML文件\n",
    "##### 安装PyYAML库\n",
    "在读取YAML文件之前，需要安装PyYAML库：\n",
    "```shell\n",
    "pip install pyyaml\n",
    "```\n",
    "##### 编写并测试读取函数\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{'base_info': {'api_name': '用户登录', 'url': '/login', 'method': 'post', 'headers': {'Content-Type': 'application/x-www-form-urlencoded'}}, 'test_cases': [{'case_name': '正确的用户名和密码', 'data': {'username': 'test_user', 'password': 'test_pass'}, 'validations': [{'contains': '登录成功'}], 'extract': ['token']}, {'case_name': '错误的用户名', 'data': {'username': 'wrong_user', 'password': 'test_pass'}, 'validations': [{'contains': '用户名或密码错误'}]}]}\n"
     ]
    }
   ],
   "source": [
    "import yaml\n",
    "\n",
    "def read_yaml(file_path):\n",
    "    \"\"\"\n",
    "    读取YAML文件并返回数据\n",
    "    :param file_path: YAML文件路径\n",
    "    :return: 文件内容\n",
    "    \"\"\"\n",
    "    try:\n",
    "        with open(file_path, 'r', encoding='utf-8') as f:\n",
    "            return yaml.safe_load(f)\n",
    "    except Exception as e:\n",
    "        print(f\"读取YAML文件出错: {e}\")\n",
    "        return None\n",
    "    \n",
    "data = read_yaml('login.yaml')\n",
    "print(data)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### 使用读取的数据发送请求"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "执行用例：正确的用户名和密码\n",
      "用例'正确的用户名和密码'执行成功！\n",
      "执行用例：错误的用户名\n",
      "用例'错误的用户名'执行成功！\n"
     ]
    }
   ],
   "source": [
    "# 封装请求方法\n",
    "import requests\n",
    "\n",
    "class APIRequest:\n",
    "    def __init__(self):\n",
    "        pass\n",
    "\n",
    "    def send_request(self, method, url, data=None, headers=None):\n",
    "        try:\n",
    "            response = requests.request(\n",
    "                method = method,\n",
    "                url = url,\n",
    "                data = data,\n",
    "                headers = headers,\n",
    "                verify = False # 忽略SSL证书验证\n",
    "            )\n",
    "            return response\n",
    "        except Exception as e:\n",
    "            print(f\"请求出错：{e}\")\n",
    "            return None\n",
    "\n",
    "# 组装数据并发送请求\n",
    "def main():\n",
    "    base_url = 'http://localhost:80' # 基础url\n",
    "    yaml_data = read_yaml('login.yaml')\n",
    "\n",
    "    if not yaml_data:\n",
    "        return\n",
    "    \n",
    "    base_info = yaml_data.get('base_info', {})\n",
    "    test_cases = yaml_data.get('test_cases', [])\n",
    "\n",
    "    api_name = base_info.get('api_name')\n",
    "    url = base_url + base_info.get('url', '')\n",
    "    method = base_info.get('method', 'get')\n",
    "    headers = base_info.get('headers', {})\n",
    "\n",
    "    api_request = APIRequest()\n",
    "\n",
    "    for case in test_cases:\n",
    "        case_name = case.get('case_name')\n",
    "        data = case.get('data', {})\n",
    "        validations = case.get('validations', [])\n",
    "        extract = case.get('extract', [])\n",
    "\n",
    "        print(f\"执行用例：{case_name}\")\n",
    "\n",
    "        response = api_request.send_request(\n",
    "            method = method,\n",
    "            url = url,\n",
    "            data = data,\n",
    "            headers = headers\n",
    "        )\n",
    "\n",
    "        if response:\n",
    "            response_text = response.text\n",
    "            # 进行断言验证\n",
    "            for validation in validations:\n",
    "                if 'contains' in validation:\n",
    "                    except_text = validation['contains']\n",
    "                    # assert except_text in response_text, f\"期望包含：{except_text}\"\n",
    "            # 进行数据提取\n",
    "            # ...(根据需要实现)\n",
    "            print(f\"用例'{case_name}'执行成功！\")\n",
    "        else:\n",
    "            print(f\"用例'{case_name}'执行失败！\")\n",
    "\n",
    "if __name__ == '__main__':\n",
    "    main()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "##### 处理编码问题\n",
    "在读取文件时，可能会遇到编码问题，常见的是utf-8和gbk编码不一致导致的错误。为确保读取正确，可以在打开文件时指定编码：\n",
    "```python\n",
    "with open(file_path, 'r', encoding='utf-8') as f:\n",
    "    # 读取文件内容\n",
    "```\n",
    "如果文件实际编码为gbk，则需要将encoding参数修改为gbk。\n",
    "建议：在创建YAML文件时，统一用utf-8编码，避免编码不一致的问题。\n",
    "#### 在python中将接口返回值写入指定的YAML文件\n",
    "在自动化测试和接口测试中，我们经常需要将接口的返回值提取并保存，以供后续测试使用。本节将介绍如如何使用python将接口的返回值写入到指定的YAML文件中。同时介绍JSON的序列化和反序列化，帮助更好地处理数据。\n",
    "##### 定义读取和写入YAML数据的工具类\n",
    "首先，需要定义一个工具类，用于读写YAML文件。将读写的方法封装到一个类中，以便重复使用。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "metadata": {},
   "outputs": [],
   "source": [
    "import yaml\n",
    "import os\n",
    "\n",
    "class YAMLUtil:\n",
    "    \"\"\"读取和写入YAML数据的工具类\"\"\"\n",
    "\n",
    "    def __init__(self, file_path = None):\n",
    "        \"\"\"\n",
    "        初始化方法，接收一个文件路径。如果未提供文件路径，使用默认的'extract.yaml'。\n",
    "        \"\"\"\n",
    "        if file_path is not None:\n",
    "            self.file_path = file_path\n",
    "        else:\n",
    "            self.file_path = 'extract.yaml' # 默认的YAML文件\n",
    "    # 在__init__方法中，允许用户在实例化类时传入一个文件路径。如果没有传入，使用默认的extract.yaml文件\n",
    "\n",
    "    # 将数据写入到YAML文件\n",
    "    def write_yaml(self, data):\n",
    "        \"\"\"\n",
    "        将数据写入到YAML文件中。\n",
    "        :param data: 要写入的数据，必须是字典类型。\n",
    "        \"\"\"\n",
    "        if not isinstance(data, dict):\n",
    "            raise ValueError(\"写入的数据必须为字典类型\")\n",
    "        \n",
    "        # 确保目录存在\n",
    "        dir_name = os.path.dirname(self.file_path)\n",
    "        if dir_name and not os.path.exists(dir_name):\n",
    "            os.makedirs(dir_name)\n",
    "\n",
    "        # 写入数据到YAML文件\n",
    "        with open(self.file_path, 'a', encoding = 'utf-8') as f:\n",
    "            yaml.dump(data, f, allow_unicode = True)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "##### 代码解析\n",
    "- 类型检查：首先检查传入的数据是否为字典类型，如果不是，抛出ValueError异常。\n",
    "- 确保目录存在：在写入文件之前，我们需要确保目录存在。如果目录不存在，使用os.makedirs创建目录。\n",
    "- 写入数据：使用with open语句打开文件，模式为'a'，表示追加写入。使用yaml.dump方法将数据写入文件，allow_unicode = Ture确保可以正确写入中文字符。\n",
    "\n",
    "##### 示例：将接口返回的Token写入到YAML文件\n",
    "假设已经有了一个登录接口，返回了一个包含token的响应。我们希望将这个token写入到YAML文件中。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "metadata": {},
   "outputs": [],
   "source": [
    "import requests\n",
    "\n",
    "# 模拟接口响应\n",
    "response = requests.Response()\n",
    "response._content = b'{\"token\": \"abc123\", \"expires_in\": 3600}'\n",
    "response.status_code = 200\n",
    "\n",
    "# 从响应中提取token\n",
    "token = response.json().get('token')\n",
    "\n",
    "# 实例化YAML工具类\n",
    "yaml_util = YAMLUtil()\n",
    "\n",
    "# 准备要写入的数据\n",
    "data_to_Write = {'token': token}\n",
    "\n",
    "# 写入数据到YAML文件\n",
    "yaml_util.write_yaml(data_to_Write)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "##### 代码解析\n",
    "- 模拟接口响应：这里模拟了一个接口响应，实际使用中可以直接使用requests库的响应对象\n",
    "- 提取token：使用response.json().get('token')提取返回值中的token\n",
    "- 实例化工具类：创建YAMLUtil的实例\n",
    "- 准备数据：将token放入字典中，键名为'token'\n",
    "- 写入数据：调用write_yaml方法，将数据写入到YAML文件中\n",
    "\n",
    "##### 文件写入模式：'w' vs 'a'\n",
    "在文件操作中，写入模式非常重要，主要有以下两种模式：\n",
    "- 'w'模式（写入模式）：每次写入都会覆盖文件原有的内容\n",
    "- 'a'模式（追加模式）：每次写入都会在文件末尾追加内容\n",
    "在上面的示例中，使用了'a'模式，确保每次写入的数据不会覆盖之前的数据，如果希望每次都覆盖，可以使用'w'模式\n",
    "```python\n",
    "with open(self.file_path, 'w', encoding = 'utf-8') as f:\n",
    "    yaml.dump(data, f, allow_unicode = True)\n",
    "```\n",
    "\n",
    "#### 扩展：JSON的序列化和反序列化\n",
    "在接口测试中，经常需要处理JSON数据，因此理解JSON的序列化和反序列化非常重要\n",
    "1. 什么是JSON序列化和反序列化\n",
    "- 序列化：将python对象（如字典）转换为JSON格式的字符串\n",
    "- 反序列化：将JSON格式的字符串转换为python对象\n",
    "2. JSON序列化示例"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{\"message\": \"成功\", \"code\": 200}\n"
     ]
    }
   ],
   "source": [
    "import json\n",
    "\n",
    "# python字典\n",
    "data = {'message': '成功', 'code': 200}\n",
    "\n",
    "# 序列化为JSON字符串\n",
    "json_str = json.dumps(data, ensure_ascii=False)\n",
    "print(json_str) # 输出：{'message': '成功', 'code': 200}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "3. 处理Unicode字符\n",
    "默认情况下，json.dumps会将非ASCII字符转换为Unicode编码，为了正确显示中文，需要设置ensure_ascii = False\n",
    "```python\n",
    "json_str = json.dumps(data, ensure_ascii=False)\n",
    "```\n",
    "4. JSON反序列化示例"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{'message': '成功', 'code': 200}\n"
     ]
    }
   ],
   "source": [
    "# JSON字符串\n",
    "json_str = '{\"message\": \"成功\", \"code\": 200}'\n",
    "\n",
    "# 反序列化为python字典\n",
    "data = json.loads(json_str)\n",
    "print(data) # 输出：{'message': '成功', 'code': 200}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### 在YAML测试用例中实现参数的动态替换\n",
    "在api测试中，将参数硬编码到测试用例中既不实用也不具备可扩展性。像令牌、加密密码或动态生成的值等参数需要以一种在运行时无需修改测试脚本就能更新或替换的方式进行处理。这就是参数的动态替换或“热加载”的作用所在。\n",
    "\n",
    "接下来将探讨如何使用python在YAML测试用例中实现参数的动态替换。我们将创建一个debugtalk模块来存放我们的动态函数、读取如令牌提取的数据、实现MD5加密、解析YAML文件中的占位符，以及使用反射动态调用函数。\n",
    "\n",
    "##### 硬编码参数的问题\n",
    "在创建api测试用例时，可能会倾向于将参数直接硬编码到YAML文件中：\n",
    "```yaml\n",
    "request:\n",
    "  method: POST\n",
    "  url: /api/login\n",
    "  headers:\n",
    "    Content-Type: application/json\n",
    "  json:\n",
    "    username: \"user1\"\n",
    "    password: \"123456\" # 硬编码的密码\n",
    "```\n",
    "但是这种方法存在几个问题：\n",
    "- 动态数据：某些参数需要在运行时生成或转换，例如加密密码或时间戳\n",
    "- 可维护性：硬编码的值使得在多个测试用例中更新或管理参数变得困难\n",
    "- 可重用性：无法在不修改文件的情况下使用不同的数据重用测试用例\n",
    "为例解决这些问题，需要一种在测试用例中动态替换参数的方法\n",
    "\n",
    "#### 引入debugtalk模块\n",
    "debugtalk模块是我们框架中的核心组件，包含了所有需要的动态函数和工具，用于参数替换。通过在此模块中定义函数，我们可以直接在YAML文件中调用它们。\n",
    "##### 创建debugtalk.py模块\n",
    "首先在项目目录中创建一个名为debugtalk.py的文件：\n",
    "```bash\n",
    "touch debugtalk.py\n",
    "```\n",
    "在debugtalk.py中，可以定义执行诸如读取数据、加密密码或生成令牌等任务的函数\n",
    "\n",
    "#### 动态读取提取的数据\n",
    "假设有一个令牌需要包含在请求的头部，但这个令牌是在运行时生成的，并在先前的api调用后存储在一个提取文件中\n",
    "##### 提取文件(extract.yaml)\n",
    "```yaml\n",
    "token: \"eyjhbGcioiJIUzI1NiIsInR5cCI6IkpXVCJ9...\"\n",
    "product_ids:\n",
    "  - \"123456\"\n",
    "  - \"234567\"\n",
    "  - \"345678\"\n",
    "```\n",
    "我们需要在发送请求时动态地读取这个令牌\n",
    "\n",
    "##### 从extract.yaml中读取数据\n",
    "在debugtalk.py中，添加一个函数来从提取文件中读取数据："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "metadata": {},
   "outputs": [],
   "source": [
    "import os\n",
    "import yaml\n",
    "\n",
    "def read_extract(key):\n",
    "    \"\"\"根据提供的键从extract.yaml中读取值。\"\"\"\n",
    "    extract_path = os.path.join(os.getcwd(), 'extract.yaml')\n",
    "    if not os.path.exists(extract_path):\n",
    "        print(\"提取文件不存在\")\n",
    "        return None\n",
    "    with open(extract_path, 'r', encoding='utf-8') as f:\n",
    "        data = yaml.safe_load(f)\n",
    "    return data.get(key)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### 实现MD5加密函数\n",
    "我们可能需要在发送请求前使用MD5加密密码。\n",
    "##### MD5加密函数\n",
    "在debugtalk.py中添加以下函数："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "metadata": {},
   "outputs": [],
   "source": [
    "import hashlib\n",
    "\n",
    "def md5_encrypt(password):\n",
    "    \"\"\"使用MD5加密密码\"\"\"\n",
    "    md5 = hashlib.md5()\n",
    "    md5.update(password.encode('utf-8'))\n",
    "    return md5.hexdigest()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### 解析并替换YAML文件中的占位符\n",
    "可以在YAML文件中使用占位符来指示需要动态替换的值\n",
    "##### 在YAML中使用占位符\n",
    "```yaml\n",
    "  request:\n",
    "    method: POST\n",
    "    url: /api/login\n",
    "    headers:\n",
    "      Content-Type: application/json\n",
    "      Authorization: \"Bearer ${read_extract(token)}\"\n",
    "    json:\n",
    "      username: \"user1\"\n",
    "      password: \"${md5_encrypt(123456)}\"\n",
    "```\n",
    "##### 解析并替换占位符\n",
    "在新的模块api_util.py中创建一个实用函数来解析和替换这些占位符："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import re\n",
    "from debugtalk import read_extract, md5_encrypt\n",
    "\n",
    "def replace_placeholder(data):\n",
    "    \"\"\"用实际值替换数据中的占位符\"\"\"\n",
    "    pattern = re.compile(r\"\\$\\{(\\w+)(\\((.*?)\\))?\\}\")\n",
    "    if isinstance(data, dict):\n",
    "        data_str = str(data)\n",
    "    else:\n",
    "        data_str = data\n",
    "\n",
    "    matches = pattern.findall(data_str)\n",
    "    for func_name, _, args in matches:\n",
    "        func = globals().get(func_name)\n",
    "        if func:\n",
    "            if args:\n",
    "                args_list = args.split(',')\n",
    "                value = func(*args_list)\n",
    "            else:\n",
    "                value = func()\n",
    "            placeholder = f\"${{{func_name}({args})}}\"\n",
    "            data_str = data_str.replace(placeholder, value)\n",
    "    return eval(data_str)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "该函数使用正则表达式查找占位符，并通过调用debugtalk.py中的相应函数来替换它们\n",
    "\n",
    "#### 使用反射动态调用函数\n",
    "python的反射功能允许我们在运行时按名称调用函数，这对于我们的参数动态替换至关重要\n",
    "\n",
    "##### 使用getattr进行反射\n",
    "在api_util中，修改replace_placeholder函数以使用getattr:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def replace_placeholder(data):\n",
    "    \"\"\"用实际值替换数据中的占位符\"\"\"\n",
    "    pattern = re.compile(r\"\\$\\{(\\w+)(\\((.*?)\\))?\\}\")\n",
    "    if isinstance(data, dict):\n",
    "        data_str = str(data)\n",
    "    else:\n",
    "        data_str = data\n",
    "\n",
    "    matches = pattern.findall(data_str)\n",
    "    for func_name, _, args in matches:\n",
    "        func = getattr(debugtalk, func_name, None)\n",
    "        if func:\n",
    "            if args:\n",
    "                args_list = [arg.strip() for arg in args.split(',')]\n",
    "                value = func(*args_list)\n",
    "            else:\n",
    "                value = func()\n",
    "            placeholder = f\"${{{func_name}({args})}}\"\n",
    "            data_str = data_str.replace(placeholder, f'\"{value}\"')\n",
    "    return eval(data_str)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "这里的getattr用于按名称从debugtalk.py中获取函数\n",
    "\n",
    "#### 处理不同的数据类型和场景\n",
    "在某些情况下，我们提取的数据可能是列表或字典，需要适当地处理它们\n",
    "##### 读取列表元素\n",
    "假设我们在extract.yaml中有一个产品ID的列表：\n",
    "```yaml\n",
    "product_ids:\n",
    "  - \"123456\"\n",
    "  - \"234567\"\n",
    "  - \"345678\"\n",
    "```\n",
    "我们可以修改read_extract以处理索引："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 35,
   "metadata": {},
   "outputs": [],
   "source": [
    "def read_extract(key, index=None):\n",
    "    \"\"\"根据提供的键和索引从extract.yaml文件中读取值\"\"\"\n",
    "    extract_path = os.path.join(os.getcwd(), 'extract.yaml')\n",
    "    if not os.path.exists(extract_path):\n",
    "        print(\"提取文件不存在\")\n",
    "        return None\n",
    "    with open(extract_path, 'r', encoding='utf-8') as f:\n",
    "        data = yaml.safe_load(f)\n",
    "    value = data.get(key)\n",
    "    if isinstance(value, list) and index is not None:\n",
    "        return value[int(index)]\n",
    "    return value"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "##### 更新占位符\n",
    "在YAML文件中：\n",
    "```yaml\n",
    "url: \"/api/products/${read_extract(product_ids, 0)}\"\n",
    "```\n",
    "现在，read_extract可以处理索引以检索特定的产品ID\n",
    "\n",
    "#### 结论\n",
    "在YAML测试用例中实现参数的动态替换，可以显著提高api测试的灵活性和可维护性。通过利用python的反射功能和解析技术，我们可以创建一个强大的测试框架，能够处理动态数据和复杂的场景。\n",
    "优势：\n",
    "- 动态处理数据：轻松管理令牌、加密密码和其他动态参数\n",
    "- 可维护性：在debugtalk.py中集中动态函数，便于更新\n",
    "- 可重用性：编写一次测试用例，使用不同的数据重复使用\n",
    "下一步：\n",
    "- 扩展功能：根据需要添加更多的实用函数\n",
    "- 错误处理：为缺少键或无效数据实现健壮的错误处理\n",
    "- 测试：为您的实用函数编写单元测试以确保可靠性\n",
    "\n",
    "### 参考资料\n",
    "[PyYAML 官方文档](https://pyyaml.org/wiki/PyYAMLDocumentation)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "deeppacket",
   "language": "python",
   "name": "deeppacket"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
